#include "glare.gles2.DrawContext.h"
#include "glare.gles2.Driver.h"
#include "glare.core.ColourValue.h"
#include "glare.core.RenderTarget.h"
#include "glare.gles2.RenderTarget.Interface.h"
#include "glare.gles2.GpuBuffer.h"
#include "glare.gles2.VertexShader.h"
#include "glare.gles2.PixelShader.h"
#include "glare.gles2.ShaderFactory.h"
#include "glare.core.ShaderInputDeclaration.h"
#include "glare.gles2.FormatMapping.h"
#include "glare.gles2.GpuResourceFactory.h"
#include "glare.gles2.ShaderResourceView.h"

namespace Glare {

	GLES2DrawContext::GLES2DrawContext(GLES2Driver* driver) 
		: _driver(driver)
		, _current_primitive_type(GL_NONE)
	{

	}

	GLES2DrawContext::~GLES2DrawContext()
	{

	}

	void GLES2DrawContext::ClearRenderTarget(RenderTarget* render_target, const ColourValue& clear_color)
	{
		// TODO: check if the given RenderTarget is not current for this GL context ???
		glClearColor(clear_color.r, clear_color.g, clear_color.b, clear_color.a);
		glClear(GL_COLOR_BUFFER_BIT);
	}
	
	void GLES2DrawContext::Draw(const uint& vertex_count, const uint& start_vertex)
	{
		glDrawArrays(_current_primitive_type, start_vertex, vertex_count);
		EGL_CHECK_ERROR;
	}

	void GLES2DrawContext::DrawIndexed(const uint& index_count, const uint& start_index, const uint& vertex_offset)
	{
		// store 16/32 bit from IA_SetIndexBuffer
		glDrawElements(_current_primitive_type, index_count, GL_UNSIGNED_INT, NULL);
		EGL_CHECK_ERROR;
	}

	void GLES2DrawContext::UpdateGpuResource(GpuResource* destination_resource, const uint& dest_sub_resource, Box* dest_box, const void* src_data, const uint& src_row_pitch, const uint& src_depth_pitch)
	{
		GLES2GpuResourceInterface* gles2_interface = static_cast<GLES2GpuResourceInterface*>(destination_resource->_GetInterface());
		gles2_interface->_UpdateImpl(dest_sub_resource, dest_box, src_data, src_row_pitch, src_depth_pitch);
	}

	void GLES2DrawContext::IA_SetIndexBuffer(GpuBuffer* buffer, const bool& format_32bit, const uint& offset)
	{
		if (!buffer)
		{
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_NONE);
			return;
		}

		GLES2GpuBuffer* gles2_buffer = static_cast<GLES2GpuBuffer*>(buffer);

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gles2_buffer->GetGLES2Id());
		EGL_CHECK_ERROR;
	}

	void GLES2DrawContext::IA_SetVertexBuffer(const GpuVertexBufferBinding& vertex_buffer_binding, const uint& start_slot)
	{
		if (!vertex_buffer_binding.Buffer)
		{
			glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);
			return;
		}

		GLES2GpuBuffer* gles2_buffer = static_cast<GLES2GpuBuffer*>(vertex_buffer_binding.Buffer);

		glBindBuffer(GL_ARRAY_BUFFER, gles2_buffer->GetGLES2Id());
		EGL_CHECK_ERROR;
	}

	void GLES2DrawContext::IA_SetVertexBuffers(const GpuVertexBufferBinding bindings[], const uint& num_bindings, const uint& start_slot)
	{
	}

	void GLES2DrawContext::IA_SetPrimitiveType(const GpuPrimitive::Type& primitive_type)
	{
		if (!_driver->GetCapabilities().IsPrimitiveSupported(primitive_type))
			_driver->WriteLog("Unsupported primitive type requested", LMT_Error);

		_current_primitive_type = GLES2FormatMapping::MapPrimitive(primitive_type);
	}

	void GLES2DrawContext::IA_SetInputDeclaration(ShaderInputDeclaration* declaration)
	{
		ShaderInputDeclaration::ElementList::iterator it = declaration->InputElements.begin();
		ShaderInputDeclaration::ElementList::iterator end = declaration->InputElements.end();

		GLES2LinkedShader* shader = _driver->GetGLES2ShaderFactory()->ActiveLinkedShader;

		if (!shader)
			return;

		for( ; it != end; ++it)
		{
			GLint attrib_location = shader->GetGLES2AttributeLocation((*it).SemanticName);

			PixelFormat pf = (*it).Format;
			GLint num_elems = GLES2FormatMapping::MapElementCount(pf);
			GLenum type = GLES2FormatMapping::MapType(pf);

			glEnableVertexAttribArray(attrib_location);
			glVertexAttribPointer(attrib_location, num_elems, type, GL_FALSE, declaration->GetTotalSize(), (GLvoid*)(*it).Offset);
		}
	}

	void GLES2DrawContext::VT_SetWorldMatrix( const Matrix4 &m )
	{
		throw Exception("The method or operation is not implemented.");
	}

	void GLES2DrawContext::VT_SetViewMatrix( const Matrix4 &m )
	{
		throw Exception("The method or operation is not implemented.");
	}

	void GLES2DrawContext::VT_SetProjectionMatrix( const Matrix4 &m )
	{
		throw Exception("The method or operation is not implemented.");
	}

	void GLES2DrawContext::VS_SetVertexShader(VertexShader* shader)
	{
		if (!shader)
		{
			_driver->GetGLES2ShaderFactory()->ActiveVertexShader = NULL;
			glUseProgram(GL_NONE);
			return;
		}

		_driver->GetGLES2ShaderFactory()->ActiveVertexShader = static_cast<GLES2VertexShader*>(shader);
		_driver->GetGLES2ShaderFactory()->SetShaders();
	}

	void GLES2DrawContext::VS_SetConstantBuffer(const uint& slot, GpuBuffer* constant_buffer)
	{
		throw Exception("The method or operation is not implemented.");
	}

	void GLES2DrawContext::RS_SetViewport(const Viewport& viewport)
	{
		glViewport((GLsizei)viewport.Left, (GLsizei)viewport.Top, (GLsizei)viewport.Width, (GLsizei)viewport.Height);
		glDepthRangef(viewport.MinDepth, viewport.MaxDepth);
	}

	void GLES2DrawContext::RS_SetViewports(uint num_viewports, const Viewport* viewport_array)
	{
	}

	void GLES2DrawContext::PS_SetPixelShader(PixelShader* shader)
	{
		if (!shader)
		{
			_driver->GetGLES2ShaderFactory()->ActivePixelShader = NULL;
			glUseProgram(GL_NONE);
			return;
		}

		_driver->GetGLES2ShaderFactory()->ActivePixelShader = static_cast<GLES2PixelShader*>(shader);
		_driver->GetGLES2ShaderFactory()->SetShaders();
	}

	void GLES2DrawContext::PS_SetSamplerState(const uint& slot, SamplerState* sampler_state)
	{
		// TODO: use correct values
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}

	void GLES2DrawContext::PS_SetShaderResource(const uint& slot, ShaderResourceView* resource_view)
	{
		GLES2ShaderResourceView* gles2_srv = static_cast<GLES2ShaderResourceView*>(resource_view);

		// TODO: this is necessary, right ?
		_driver->GetGLES2ShaderFactory()->SetShaders();
		EGL_CHECK_ERROR;

		// TODO: how to derive sampler name
		//int sampler_location = glGetUniformLocation(_driver->GetGLES2ShaderFactory()->ActiveShader->_shader_id, "tex");
		//EGL_CHECK_ERROR;

		glActiveTexture(GL_TEXTURE0 + slot);
		EGL_CHECK_ERROR;

		//glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, gles2_srv->GetGLES2GpuResourceInterface()->GetGLES2Id());
		EGL_CHECK_ERROR;

		glUniform1i(slot, slot);
		EGL_CHECK_ERROR;
	}

	void GLES2DrawContext::OM_SetRenderTarget(RenderTarget* render_target, DepthBuffer* depth_buffer)
	{
		if (!render_target)
			return;

		GLES2RenderTargetInterface* gles2_interface = static_cast<GLES2RenderTargetInterface*>(render_target->_GetInterface());

		if (!gles2_interface)
			return;

		gles2_interface->MakeCurrent();
	}

	void GLES2DrawContext::OM_SetMultiRenderTargets(RenderTarget** render_targets, int num_targets, DepthBuffer* depth_buffer)
	{
		throw Exception("not supported");
	}

	Driver* GLES2DrawContext::GetDriver() const
	{
		return _driver;
	}
}